﻿//#define HEAD_UINT16
#define HEAD_UINT32
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace Sttplay.Transmission.TCP
{
    public class TCPReceiver
    {
        protected Socket socket;
        private const int BUFF_SIZE = 65535;
        private static byte[] cache = new byte[BUFF_SIZE];
        protected Action<byte[]> recvCallback;
        protected Action<string> errorLogCallback;
        private byte[] data;
        protected bool isPacket;
        private List<byte> packetCache = new List<byte>();
        private List<Task<int>> sendTasks = new List<Task<int>>();
        private bool isSendError = false;
        public int SendTaskCount { get { return sendTasks.Count; } }
        public Socket Socket { get { return socket; } }
        public TCPReceiver() { }
        public TCPReceiver(Socket s)
        {
            socket = s;
            socket.ReceiveTimeout = 10;
        }

        protected void Reset()
        {
            isSendError = false;
            sendTasks.Clear();
            packetCache.Clear();
        }

        public virtual bool Send(byte[] buffer, int offset, int size)
        {
            if (isSendError)
                return false;
            try
            {
                //SocketError error;
                if (isPacket)
                {
                    var sendBuffer = AssemblyBuffer(buffer, offset, size);
                    //socket.BeginSend(sendBuffer, 0, sendBuffer.Length, SocketFlags.None, out error, null, null);
                    sendTasks.Add(socket.SendAsync(new ArraySegment<byte>(sendBuffer), SocketFlags.None));
                }
                else
                {
                    //socket.BeginSend(buffer, offset, size, SocketFlags.None, out error, null, null);
                    sendTasks.Add(socket.SendAsync(new ArraySegment<byte>(buffer), SocketFlags.None));
                }
                //if (error != SocketError.Success)
                //    throw new Exception("send error");
            }
            catch (Exception ex)
            {
                errorLogCallback(ex.ToString());
                return false;
            }
            return true;
        }

        public virtual void Update()
        {
            for (int i = 0; i < sendTasks.Count; i++)
            {
                if (sendTasks[i].IsCompleted)
                {
                    try
                    {
                        if (sendTasks[i].Result <= 0)
                            isSendError = true;
                    }
                    catch
                    {
                        isSendError = true;
                    }
                   
                    sendTasks.RemoveAt(i);
                    i--;
                }
            }
        }

        public SocketError Receive(ref int len)
        {
            SocketError error;
            len = socket.Receive(cache, 0, BUFF_SIZE, SocketFlags.None, out error);
            if (len > 0)
            {
                data = new byte[len];
                Buffer.BlockCopy(cache, 0, data, 0, len);
                if (isPacket)
                    ProcessPacket(data);
                else
                    recvCallback(data);
            }
            return error;
        }

        private void ProcessPacket(byte[] data)
        {
            packetCache.AddRange(data);

            while (true)
            {
#if HEAD_UINT16
                int typeSize = sizeof(ushort);
#else
                int typeSize = sizeof(uint);
#endif
                if (packetCache.Count < typeSize)
                    return;

                byte[] dataCacheArray = packetCache.ToArray();
#if HEAD_UINT16
                int len = BitConverter.ToUInt16(dataCacheArray, 0);
#else
                int len = (int)BitConverter.ToUInt32(dataCacheArray, 0);
#endif

                if (dataCacheArray.Length - typeSize < len)
                    return;

                packetCache.RemoveRange(0, len + typeSize);
                byte[] msgData = new byte[len];
                Buffer.BlockCopy(dataCacheArray, typeSize, msgData, 0, len);
                recvCallback(msgData);
            }
        }

        private byte[] AssemblyBuffer(byte[] buffer, int offset, int size)
        {
            using (MemoryStream ms = new MemoryStream())
            {

#if HEAD_UINT16
                ushort bsize = (ushort)size;
                int typeSize = sizeof(ushort);
#else
                uint bsize = (uint)size;
                int typeSize = sizeof(uint);
#endif
                byte[] sizebytes = BitConverter.GetBytes(bsize);
                ms.Write(sizebytes, 0, typeSize);
                ms.Write(buffer, offset, size);
                ms.Position = 0;
                byte[] retbuffer = new byte[ms.Length];
                ms.Read(retbuffer, 0, retbuffer.Length);
                return retbuffer;
            }
        }
    }

}

